/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * ArffPanel.java
 * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.arffviewer;

import weka.core.Instances;
import weka.core.Undoable;
import weka.core.Utils;
import weka.gui.ComponentHelper;
import weka.gui.JTableHelper;
import weka.gui.ListSelectorDialog;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TableModelEvent;

/**
 * A Panel representing an ARFF-Table and the associated filename.
 * 
 * 
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 7369 $
 */

public class ArffPanel extends JPanel implements ActionListener,
		ChangeListener, MouseListener, Undoable {

	/** for serialization */
	static final long serialVersionUID = -4697041150989513939L;

	/** the name of the tab for instances that were set directly */
	public final static String TAB_INSTANCES = Messages.getInstance()
			.getString("ArffPanel_TabInstanses_Text");

	/** the underlying table */
	private ArffTable m_TableArff;
	/** the popup menu for the header row */
	private JPopupMenu m_PopupHeader;
	/** the popup menu for the data rows */
	private JPopupMenu m_PopupRows;
	/** displays the relation name */
	private JLabel m_LabelName;

	// menu items
	private JMenuItem menuItemMean;
	private JMenuItem menuItemSetAllValues;
	private JMenuItem menuItemSetMissingValues;
	private JMenuItem menuItemReplaceValues;
	private JMenuItem menuItemRenameAttribute;
	private JMenuItem menuItemAttributeAsClass;
	private JMenuItem menuItemDeleteAttribute;
	private JMenuItem menuItemDeleteAttributes;
	private JMenuItem menuItemSortInstances;
	private JMenuItem menuItemDeleteSelectedInstance;
	private JMenuItem menuItemDeleteAllSelectedInstances;
	private JMenuItem menuItemSearch;
	private JMenuItem menuItemClearSearch;
	private JMenuItem menuItemUndo;
	private JMenuItem menuItemCopy;
	private JMenuItem menuItemOptimalColWidth;
	private JMenuItem menuItemOptimalColWidths;

	/** the filename used in the title */
	private String m_Filename;
	/** the title prefix */
	private String m_Title;
	/** the currently selected column */
	private int m_CurrentCol;
	/** flag for whether data got changed */
	private boolean m_Changed;
	/** the listeners that listen for modifications */
	private HashSet m_ChangeListeners;
	/** the string used in the last search */
	private String m_LastSearch;
	/** the string used in the last replace */
	private String m_LastReplace;

	/**
	 * initializes the panel with no data
	 */
	public ArffPanel() {
		super();

		initialize();
		createPanel();
	}

	/**
	 * initializes the panel and loads the specified file
	 * 
	 * @param filename
	 *            the file to load
	 */
	public ArffPanel(String filename) {
		this();

		loadFile(filename);
	}

	/**
	 * initializes the panel with the given data
	 * 
	 * @param data
	 *            the data to use
	 */
	public ArffPanel(Instances data) {
		this();

		m_Filename = "";

		setInstances(data);
	}

	/**
	 * any member variables are initialized here
	 */
	protected void initialize() {
		m_Filename = "";
		m_Title = "";
		m_CurrentCol = -1;
		m_LastSearch = "";
		m_LastReplace = "";
		m_Changed = false;
		m_ChangeListeners = new HashSet();
	}

	/**
	 * creates all the components in the frame
	 */
	protected void createPanel() {
		JScrollPane pane;

		setLayout(new BorderLayout());

		menuItemMean = new JMenuItem(Messages.getInstance().getString(
				"ArffPanel_GetMean_JMenuItem_Text"));
		menuItemMean.addActionListener(this);
		menuItemSetAllValues = new JMenuItem(Messages.getInstance().getString(
				"ArffPanel_SetAllValuesTo_JMenuItem_Text"));
		menuItemSetAllValues.addActionListener(this);
		menuItemSetMissingValues = new JMenuItem(Messages.getInstance()
				.getString("ArffPanel_SetMissingValues_JMenuItem_Text"));
		menuItemSetMissingValues.addActionListener(this);
		menuItemReplaceValues = new JMenuItem(Messages.getInstance().getString(
				"ArffPanel_ReplaceValues_JMenuItem_Text"));
		menuItemReplaceValues.addActionListener(this);
		menuItemRenameAttribute = new JMenuItem(Messages.getInstance()
				.getString("ArffPanel_RenameAttribute_JMenuItem_Text"));
		menuItemRenameAttribute.addActionListener(this);
		menuItemAttributeAsClass = new JMenuItem(Messages.getInstance()
				.getString("ArffPanel_AttributeAsClass_JMenuItem_Text"));
		menuItemAttributeAsClass.addActionListener(this);
		menuItemDeleteAttribute = new JMenuItem(Messages.getInstance()
				.getString("ArffPanel_DeleteAttribute_JMenuItem_Text"));
		menuItemDeleteAttribute.addActionListener(this);
		menuItemDeleteAttributes = new JMenuItem(Messages.getInstance()
				.getString("ArffPanel_DeleteAttributes_JMenuItem_Text"));
		menuItemDeleteAttributes.addActionListener(this);
		menuItemSortInstances = new JMenuItem(Messages.getInstance().getString(
				"ArffPanel_SortInstances_JMenuItem_Text"));
		menuItemSortInstances.addActionListener(this);
		menuItemOptimalColWidth = new JMenuItem(Messages.getInstance()
				.getString("ArffPanel_OptimalColWidth_JMenuItem_Text"));
		menuItemOptimalColWidth.addActionListener(this);
		menuItemOptimalColWidths = new JMenuItem(Messages.getInstance()
				.getString("ArffPanel_OptimalColWidths_JMenuItem_Text"));
		menuItemOptimalColWidths.addActionListener(this);

		// row popup
		menuItemUndo = new JMenuItem(Messages.getInstance().getString(
				"ArffPanel_Undo_JMenuItem_Text"));
		menuItemUndo.addActionListener(this);
		menuItemCopy = new JMenuItem(Messages.getInstance().getString(
				"ArffPanel_Copy_JMenuItem_Text"));
		menuItemCopy.addActionListener(this);
		menuItemSearch = new JMenuItem(Messages.getInstance().getString(
				"ArffPanel_Search_JMenuItem_Text"));
		menuItemSearch.addActionListener(this);
		menuItemClearSearch = new JMenuItem(Messages.getInstance().getString(
				"ArffPanel_ClearSearch_JMenuItem_Text"));
		menuItemClearSearch.addActionListener(this);
		menuItemDeleteSelectedInstance = new JMenuItem(Messages.getInstance()
				.getString("ArffPanel_DeleteSelectedInstance_JMenuItem_Text"));
		menuItemDeleteSelectedInstance.addActionListener(this);
		menuItemDeleteAllSelectedInstances = new JMenuItem(Messages
				.getInstance().getString(
						"ArffPanel_DeleteAllSelectedInstances_JMenuItem_Text"));
		menuItemDeleteAllSelectedInstances.addActionListener(this);

		// table
		m_TableArff = new ArffTable();
		m_TableArff.setToolTipText(Messages.getInstance().getString(
				"ArffPanel_ToolTipText_Text"));
		m_TableArff.getTableHeader().addMouseListener(this);
		m_TableArff
				.getTableHeader()
				.setToolTipText(
						Messages.getInstance().getString(
								"ArffPanel_TableHeader_ToolTipText_Text")
								+ "<html><b>Sort view:</b> left click = ascending / Shift + left click = descending<br><b>Menu:</b> right click (or left+alt)</html>");
		m_TableArff.getTableHeader().setDefaultRenderer(
				new ArffTableCellRenderer());
		m_TableArff.addChangeListener(this);
		m_TableArff.addMouseListener(this);
		pane = new JScrollPane(m_TableArff);
		add(pane, BorderLayout.CENTER);

		// relation name
		m_LabelName = new JLabel();
		add(m_LabelName, BorderLayout.NORTH);
	}

	/**
	 * initializes the popup menus
	 */
	private void initPopupMenus() {
		// header popup
		m_PopupHeader = new JPopupMenu();
		m_PopupHeader.addMouseListener(this);
		m_PopupHeader.add(menuItemMean);
		if (!isReadOnly()) {
			m_PopupHeader.addSeparator();
			m_PopupHeader.add(menuItemSetAllValues);
			m_PopupHeader.add(menuItemSetMissingValues);
			m_PopupHeader.add(menuItemReplaceValues);
			m_PopupHeader.addSeparator();
			m_PopupHeader.add(menuItemRenameAttribute);
			m_PopupHeader.add(menuItemAttributeAsClass);
			m_PopupHeader.add(menuItemDeleteAttribute);
			m_PopupHeader.add(menuItemDeleteAttributes);
			m_PopupHeader.add(menuItemSortInstances);
		}
		m_PopupHeader.addSeparator();
		m_PopupHeader.add(menuItemOptimalColWidth);
		m_PopupHeader.add(menuItemOptimalColWidths);

		// row popup
		m_PopupRows = new JPopupMenu();
		m_PopupRows.addMouseListener(this);
		if (!isReadOnly()) {
			m_PopupRows.add(menuItemUndo);
			m_PopupRows.addSeparator();
		}
		m_PopupRows.add(menuItemCopy);
		m_PopupRows.addSeparator();
		m_PopupRows.add(menuItemSearch);
		m_PopupRows.add(menuItemClearSearch);
		if (!isReadOnly()) {
			m_PopupRows.addSeparator();
			m_PopupRows.add(menuItemDeleteSelectedInstance);
			m_PopupRows.add(menuItemDeleteAllSelectedInstances);
		}
	}

	/**
	 * sets the enabled/disabled state of the menu items
	 */
	private void setMenu() {
		boolean isNumeric;
		boolean hasColumns;
		boolean hasRows;
		boolean attSelected;
		ArffSortedTableModel model;
		boolean isNull;

		model = (ArffSortedTableModel) m_TableArff.getModel();
		isNull = (model.getInstances() == null);
		hasColumns = !isNull && (model.getInstances().numAttributes() > 0);
		hasRows = !isNull && (model.getInstances().numInstances() > 0);
		attSelected = hasColumns && (m_CurrentCol > 0);
		isNumeric = attSelected
				&& (model.getAttributeAt(m_CurrentCol).isNumeric());

		menuItemUndo.setEnabled(canUndo());
		menuItemCopy.setEnabled(true);
		menuItemSearch.setEnabled(true);
		menuItemClearSearch.setEnabled(true);
		menuItemMean.setEnabled(isNumeric);
		menuItemSetAllValues.setEnabled(attSelected);
		menuItemSetMissingValues.setEnabled(attSelected);
		menuItemReplaceValues.setEnabled(attSelected);
		menuItemRenameAttribute.setEnabled(attSelected);
		menuItemDeleteAttribute.setEnabled(attSelected);
		menuItemDeleteAttributes.setEnabled(attSelected);
		menuItemSortInstances.setEnabled(hasRows && attSelected);
		menuItemDeleteSelectedInstance.setEnabled(hasRows
				&& m_TableArff.getSelectedRow() > -1);
		menuItemDeleteAllSelectedInstances.setEnabled(hasRows
				&& (m_TableArff.getSelectedRows().length > 0));
	}

	/**
	 * returns the table component
	 * 
	 * @return the table
	 */
	public ArffTable getTable() {
		return m_TableArff;
	}

	/**
	 * returns the title for the Tab, i.e. the filename
	 * 
	 * @return the title for the tab
	 */
	public String getTitle() {
		return m_Title;
	}

	/**
	 * returns the filename
	 * 
	 * @return the filename
	 */
	public String getFilename() {
		return m_Filename;
	}

	/**
	 * sets the filename
	 * 
	 * @param filename
	 *            the new filename
	 */
	public void setFilename(String filename) {
		m_Filename = filename;
		createTitle();
	}

	/**
	 * returns the instances of the panel, if none then NULL
	 * 
	 * @return the instances of the panel
	 */
	public Instances getInstances() {
		Instances result;

		result = null;

		if (m_TableArff.getModel() != null)
			result = ((ArffSortedTableModel) m_TableArff.getModel())
					.getInstances();

		return result;
	}

	/**
	 * displays the given instances, i.e. creates a tab with the title
	 * TAB_INSTANCES. if one already exists it closes it.<br>
	 * if a different instances object is used here, don't forget to clear the
	 * undo-history by calling <code>clearUndo()</code>
	 * 
	 * @param data
	 *            the instances to display
	 * @see #TAB_INSTANCES
	 * @see #clearUndo()
	 */
	public void setInstances(Instances data) {
		ArffSortedTableModel model;

		m_Filename = TAB_INSTANCES;

		createTitle();
		model = new ArffSortedTableModel(data);

		m_TableArff.setModel(model);
		clearUndo();
		setChanged(false);
		createName();
	}

	/**
	 * returns a list with the attributes
	 * 
	 * @return a list of the attributes
	 */
	public Vector getAttributes() {
		Vector result;
		int i;

		result = new Vector();
		for (i = 0; i < getInstances().numAttributes(); i++)
			result.add(getInstances().attribute(i).name());
		Collections.sort(result);

		return result;
	}

	/**
	 * can only reset the changed state to FALSE
	 * 
	 * @param changed
	 *            if false, resets the changed state
	 */
	public void setChanged(boolean changed) {
		if (!changed) {
			this.m_Changed = changed;
			createTitle();
		}
	}

	/**
	 * returns whether the content of the panel was changed
	 * 
	 * @return true if the content was changed
	 */
	public boolean isChanged() {
		return m_Changed;
	}

	/**
	 * returns whether the model is read-only
	 * 
	 * @return true if model is read-only
	 */
	public boolean isReadOnly() {
		if (m_TableArff == null)
			return true;
		else
			return ((ArffSortedTableModel) m_TableArff.getModel()).isReadOnly();
	}

	/**
	 * sets whether the model is read-only
	 * 
	 * @param value
	 *            if true the model is set to read-only
	 */
	public void setReadOnly(boolean value) {
		if (m_TableArff != null)
			((ArffSortedTableModel) m_TableArff.getModel()).setReadOnly(value);
	}

	/**
	 * returns whether undo support is enabled
	 * 
	 * @return true if undo is enabled
	 */
	public boolean isUndoEnabled() {
		return ((ArffSortedTableModel) m_TableArff.getModel()).isUndoEnabled();
	}

	/**
	 * sets whether undo support is enabled
	 * 
	 * @param enabled
	 *            whether to enable/disable undo support
	 */
	public void setUndoEnabled(boolean enabled) {
		((ArffSortedTableModel) m_TableArff.getModel()).setUndoEnabled(enabled);
	}

	/**
	 * removes the undo history
	 */
	public void clearUndo() {
		((ArffSortedTableModel) m_TableArff.getModel()).clearUndo();
	}

	/**
	 * returns whether an undo is possible
	 * 
	 * @return true if undo is possible
	 */
	public boolean canUndo() {
		return ((ArffSortedTableModel) m_TableArff.getModel()).canUndo();
	}

	/**
	 * performs an undo action
	 */
	public void undo() {
		if (canUndo()) {
			((ArffSortedTableModel) m_TableArff.getModel()).undo();

			// notify about update
			notifyListener();
		}
	}

	/**
	 * adds the current state of the instances to the undolist
	 */
	public void addUndoPoint() {
		((ArffSortedTableModel) m_TableArff.getModel()).addUndoPoint();

		// update menu
		setMenu();
	}

	/**
	 * sets the title (i.e. filename)
	 */
	private void createTitle() {
		File file;

		if (m_Filename.equals("")) {
			m_Title = Messages.getInstance().getString(
					"ArffPanel_CreateTitle_Title_Text");
		} else if (m_Filename.equals(TAB_INSTANCES)) {
			m_Title = TAB_INSTANCES;
		} else {
			try {
				file = new File(m_Filename);
				m_Title = file.getName();
			} catch (Exception e) {
				m_Title = Messages.getInstance().getString(
						"ArffPanel_CreateTitle_Title_Text");
			}
		}

		if (isChanged())
			m_Title += " *";
	}

	/**
	 * sets the relation name
	 */
	private void createName() {
		ArffSortedTableModel model;

		model = (ArffSortedTableModel) m_TableArff.getModel();
		if ((model != null) && (model.getInstances() != null))
			m_LabelName.setText(Messages.getInstance().getString(
					"ArffPanel_CreateName_Text")
					+ model.getInstances().relationName());
		else
			m_LabelName.setText("");
	}

	/**
	 * loads the specified file into the table
	 * 
	 * @param filename
	 *            the file to load
	 */
	private void loadFile(String filename) {
		ArffSortedTableModel model;

		this.m_Filename = filename;

		createTitle();

		if (filename.equals(""))
			model = null;
		else
			model = new ArffSortedTableModel(filename);

		m_TableArff.setModel(model);
		setChanged(false);
		createName();
	}

	/**
	 * calculates the mean of the given numeric column
	 */
	private void calcMean() {
		ArffSortedTableModel model;
		int i;
		double mean;

		// no column selected?
		if (m_CurrentCol == -1)
			return;

		model = (ArffSortedTableModel) m_TableArff.getModel();

		// not numeric?
		if (!model.getAttributeAt(m_CurrentCol).isNumeric())
			return;

		mean = 0;
		for (i = 0; i < model.getRowCount(); i++)
			mean += model.getInstances().instance(i).value(m_CurrentCol - 1);
		mean = mean / model.getRowCount();

		// show result
		ComponentHelper.showMessageBox(
				getParent(),
				Messages.getInstance().getString(
						"ArffPanel_CalcMean_Text_First"),
				Messages.getInstance().getString(
						"ArffPanel_CalcMean_Text_Second")
						+ m_TableArff.getPlainColumnName(m_CurrentCol)
						+ Messages.getInstance().getString(
								"ArffPanel_CalcMean_Text_Third")
						+ Utils.doubleToString(mean, 3),
				JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
	}

	/**
	 * sets the specified values in a column to a new value
	 * 
	 * @param o
	 *            the menu item
	 */
	private void setValues(Object o) {
		String msg;
		String title;
		String value;
		String valueNew;
		int i;
		ArffSortedTableModel model;

		value = "";
		valueNew = "";

		if (o == menuItemSetMissingValues) {
			title = Messages.getInstance().getString(
					"ArffPanel_SetValues_Title_Text_First");
			msg = Messages.getInstance().getString(
					"ArffPanel_SetValues_Message_Text_First");
		} else if (o == menuItemSetAllValues) {
			title = Messages.getInstance().getString(
					"ArffPanel_SetValues_Title_Text_Second");
			msg = Messages.getInstance().getString(
					"ArffPanel_SetValues_Message_Text_Second");
		} else if (o == menuItemReplaceValues) {
			title = Messages.getInstance().getString(
					"ArffPanel_SetValues_Title_Text_Third");
			msg = Messages.getInstance().getString(
					"ArffPanel_SetValues_Message_Text_Third");
		} else
			return;

		value = ComponentHelper.showInputBox(m_TableArff.getParent(), title,
				msg, m_LastSearch);

		// cancelled?
		if (value == null)
			return;

		m_LastSearch = value;

		// replacement
		if (o == menuItemReplaceValues) {
			valueNew = ComponentHelper
					.showInputBox(
							m_TableArff.getParent(),
							title,
							Messages.getInstance()
									.getString(
											"ArffPanel_SetValues_ComponentHelperShowInputBox_Text"),
							m_LastReplace);
			if (valueNew == null)
				return;
			m_LastReplace = valueNew;
		}

		model = (ArffSortedTableModel) m_TableArff.getModel();
		model.setNotificationEnabled(false);

		// undo
		addUndoPoint();
		model.setUndoEnabled(false);
		String valueCopy = value;
		String valueNewCopy = valueNew;
		// set value
		for (i = 0; i < m_TableArff.getRowCount(); i++) {
			if (o == menuItemSetAllValues) {
				if (valueCopy.equals("NaN") || valueCopy.equals("?")) {
					value = null;
				}
				model.setValueAt(value, i, m_CurrentCol);
			} else if ((o == menuItemSetMissingValues)
					&& model.isMissingAt(i, m_CurrentCol))
				model.setValueAt(value, i, m_CurrentCol);
			else if ((o == menuItemReplaceValues)
					&& model.getValueAt(i, m_CurrentCol).toString()
							.equals(value)) {
				if (valueNewCopy.equals("NaN") || valueNewCopy.equals("?")) {
					valueNew = null;
				}
				model.setValueAt(valueNew, i, m_CurrentCol);
			}
		}
		model.setUndoEnabled(true);
		model.setNotificationEnabled(true);
		model.notifyListener(new TableModelEvent(model, 0, model.getRowCount(),
				m_CurrentCol, TableModelEvent.UPDATE));

		// refresh
		m_TableArff.repaint();
	}

	/**
	 * deletes the currently selected attribute
	 */
	public void deleteAttribute() {
		ArffSortedTableModel model;

		// no column selected?
		if (m_CurrentCol == -1)
			return;

		model = (ArffSortedTableModel) m_TableArff.getModel();

		// really an attribute column?
		if (model.getAttributeAt(m_CurrentCol) == null)
			return;

		// really?
		if (ComponentHelper
				.showMessageBox(
						getParent(),
						Messages.getInstance()
								.getString(
										"ArffPanel_DeleteAttribute_ComponentHelperShowMessageBox_Text_First"),
						Messages.getInstance()
								.getString(
										"ArffPanel_DeleteAttribute_ComponentHelperShowMessageBox_Text_Second")
								+ model.getAttributeAt(m_CurrentCol).name()
								+ Messages
										.getInstance()
										.getString(
												"ArffPanel_DeleteAttribute_ComponentHelperShowMessageBox_Text_Third"),
						JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
			return;

		setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		model.deleteAttributeAt(m_CurrentCol);
		setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

	/**
	 * deletes the chosen attributes
	 */
	public void deleteAttributes() {
		ListSelectorDialog dialog;
		ArffSortedTableModel model;
		Object[] atts;
		int[] indices;
		int i;
		JList list;
		int result;

		list = new JList(getAttributes());
		dialog = new ListSelectorDialog(null, list);
		result = dialog.showDialog();

		if (result != ListSelectorDialog.APPROVE_OPTION)
			return;

		atts = list.getSelectedValues();

		// really?
		if (ComponentHelper
				.showMessageBox(
						getParent(),
						Messages.getInstance()
								.getString(
										"ArffPanel_DeleteAttributes_ComponentHelperShowMessageBox_Text_First"),
						Messages.getInstance()
								.getString(
										"ArffPanel_DeleteAttributes_ComponentHelperShowMessageBox_Text_Second")
								+ atts.length
								+ Messages
										.getInstance()
										.getString(
												"ArffPanel_DeleteAttributes_ComponentHelperShowMessageBox_Text_Third"),
						JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
			return;

		model = (ArffSortedTableModel) m_TableArff.getModel();
		indices = new int[atts.length];
		for (i = 0; i < atts.length; i++)
			indices[i] = model.getAttributeColumn(atts[i].toString());

		setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		model.deleteAttributes(indices);
		setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

	/**
	 * sets the current attribute as class attribute, i.e. it moves it to the
	 * end of the attributes
	 */
	public void attributeAsClass() {
		ArffSortedTableModel model;

		// no column selected?
		if (m_CurrentCol == -1)
			return;

		model = (ArffSortedTableModel) m_TableArff.getModel();

		// really an attribute column?
		if (model.getAttributeAt(m_CurrentCol) == null)
			return;

		setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		model.attributeAsClassAt(m_CurrentCol);
		setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

	/**
	 * renames the current attribute
	 */
	public void renameAttribute() {
		ArffSortedTableModel model;
		String newName;

		// no column selected?
		if (m_CurrentCol == -1)
			return;

		model = (ArffSortedTableModel) m_TableArff.getModel();

		// really an attribute column?
		if (model.getAttributeAt(m_CurrentCol) == null)
			return;

		newName = ComponentHelper
				.showInputBox(
						getParent(),
						Messages.getInstance()
								.getString(
										"ArffPanel_RenameAttribute_ComponentHelperShowInputBox_Text_First"),
						Messages.getInstance()
								.getString(
										"ArffPanel_RenameAttribute_ComponentHelperShowInputBox_Text_Second"),
						model.getAttributeAt(m_CurrentCol).name());
		if (newName == null)
			return;

		setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		model.renameAttributeAt(m_CurrentCol, newName);
		setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

	/**
	 * deletes the currently selected instance
	 */
	public void deleteInstance() {
		int index;

		index = m_TableArff.getSelectedRow();
		if (index == -1)
			return;

		((ArffSortedTableModel) m_TableArff.getModel()).deleteInstanceAt(index);
	}

	/**
	 * deletes all the currently selected instances
	 */
	public void deleteInstances() {
		int[] indices;

		if (m_TableArff.getSelectedRow() == -1)
			return;

		indices = m_TableArff.getSelectedRows();
		((ArffSortedTableModel) m_TableArff.getModel())
				.deleteInstances(indices);
	}

	/**
	 * sorts the instances via the currently selected column
	 */
	public void sortInstances() {
		if (m_CurrentCol == -1)
			return;

		((ArffSortedTableModel) m_TableArff.getModel())
				.sortInstances(m_CurrentCol);
	}

	/**
	 * copies the content of the selection to the clipboard
	 */
	public void copyContent() {
		StringSelection selection;
		Clipboard clipboard;

		selection = getTable().getStringSelection();
		if (selection == null)
			return;

		clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
		clipboard.setContents(selection, selection);
	}

	/**
	 * searches for a string in the cells
	 */
	public void search() {
		String searchString;

		// display dialog
		searchString = ComponentHelper
				.showInputBox(
						getParent(),
						Messages.getInstance()
								.getString(
										"ArffPanel_Search_ComponentHelperShowInputBox_Text_First"),
						Messages.getInstance()
								.getString(
										"ArffPanel_Search_ComponentHelperShowInputBox_Text_Second"),
						m_LastSearch);
		if (searchString != null)
			m_LastSearch = searchString;

		getTable().setSearchString(searchString);
	}

	/**
	 * clears the search, i.e. resets the found cells
	 */
	public void clearSearch() {
		getTable().setSearchString("");
	}

	/**
	 * calculates the optimal column width for the current column
	 */
	public void setOptimalColWidth() {
		// no column selected?
		if (m_CurrentCol == -1)
			return;

		JTableHelper.setOptimalColumnWidth(getTable(), m_CurrentCol);
	}

	/**
	 * calculates the optimal column widths for all columns
	 */
	public void setOptimalColWidths() {
		JTableHelper.setOptimalColumnWidth(getTable());
	}

	/**
	 * invoked when an action occurs
	 * 
	 * @param e
	 *            the action event
	 */
	public void actionPerformed(ActionEvent e) {
		Object o;

		o = e.getSource();

		if (o == menuItemMean)
			calcMean();
		else if (o == menuItemSetAllValues)
			setValues(menuItemSetAllValues);
		else if (o == menuItemSetMissingValues)
			setValues(menuItemSetMissingValues);
		else if (o == menuItemReplaceValues)
			setValues(menuItemReplaceValues);
		else if (o == menuItemRenameAttribute)
			renameAttribute();
		else if (o == menuItemAttributeAsClass)
			attributeAsClass();
		else if (o == menuItemDeleteAttribute)
			deleteAttribute();
		else if (o == menuItemDeleteAttributes)
			deleteAttributes();
		else if (o == menuItemDeleteSelectedInstance)
			deleteInstance();
		else if (o == menuItemDeleteAllSelectedInstances)
			deleteInstances();
		else if (o == menuItemSortInstances)
			sortInstances();
		else if (o == menuItemSearch)
			search();
		else if (o == menuItemClearSearch)
			clearSearch();
		else if (o == menuItemUndo)
			undo();
		else if (o == menuItemCopy)
			copyContent();
		else if (o == menuItemOptimalColWidth)
			setOptimalColWidth();
		else if (o == menuItemOptimalColWidths)
			setOptimalColWidths();
	}

	/**
	 * Invoked when a mouse button has been pressed and released on a component
	 * 
	 * @param e
	 *            the mouse event
	 */
	public void mouseClicked(MouseEvent e) {
		int col;
		boolean popup;

		col = m_TableArff.columnAtPoint(e.getPoint());
		popup = ((e.getButton() == MouseEvent.BUTTON3) && (e.getClickCount() == 1))
				|| ((e.getButton() == MouseEvent.BUTTON1)
						&& (e.getClickCount() == 1) && e.isAltDown()
						&& !e.isControlDown() && !e.isShiftDown());
		popup = popup && (getInstances() != null);

		if (e.getSource() == m_TableArff.getTableHeader()) {
			m_CurrentCol = col;

			// Popup-Menu
			if (popup) {
				e.consume();
				setMenu();
				initPopupMenus();
				m_PopupHeader.show(e.getComponent(), e.getX(), e.getY());
			}
		} else if (e.getSource() == m_TableArff) {
			// Popup-Menu
			if (popup) {
				e.consume();
				setMenu();
				initPopupMenus();
				m_PopupRows.show(e.getComponent(), e.getX(), e.getY());
			}
		}

		// highlihgt column
		if ((e.getButton() == MouseEvent.BUTTON1) && (e.getClickCount() == 1)
				&& (!e.isAltDown()) && (col > -1)) {
			m_TableArff.setSelectedColumn(col);
		}
	}

	/**
	 * Invoked when the mouse enters a component.
	 * 
	 * @param e
	 *            the mouse event
	 */
	public void mouseEntered(MouseEvent e) {
	}

	/**
	 * Invoked when the mouse exits a component
	 * 
	 * @param e
	 *            the mouse event
	 */
	public void mouseExited(MouseEvent e) {
	}

	/**
	 * Invoked when a mouse button has been pressed on a component
	 * 
	 * @param e
	 *            the mouse event
	 */
	public void mousePressed(MouseEvent e) {
	}

	/**
	 * Invoked when a mouse button has been released on a component.
	 * 
	 * @param e
	 *            the mouse event
	 */
	public void mouseReleased(MouseEvent e) {
	}

	/**
	 * Invoked when the target of the listener has changed its state.
	 * 
	 * @param e
	 *            the change event
	 */
	public void stateChanged(ChangeEvent e) {
		m_Changed = true;
		createTitle();
		notifyListener();
	}

	/**
	 * notfies all listener of the change
	 */
	public void notifyListener() {
		Iterator iter;

		iter = m_ChangeListeners.iterator();
		while (iter.hasNext())
			((ChangeListener) iter.next()).stateChanged(new ChangeEvent(this));
	}

	/**
	 * Adds a ChangeListener to the panel
	 * 
	 * @param l
	 *            the listener to add
	 */
	public void addChangeListener(ChangeListener l) {
		m_ChangeListeners.add(l);
	}

	/**
	 * Removes a ChangeListener from the panel
	 * 
	 * @param l
	 *            the listener to remove
	 */
	public void removeChangeListener(ChangeListener l) {
		m_ChangeListeners.remove(l);
	}
}
